/**
 * 版权所有(C) 晓祥工作室 2010-2020<br>
 * Copyright 2010-2020 XiaoXiang Workroom.<br>
 * 创建日期 2011-9-26
 */
package xx.services;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import xx.services.util.Constants;
import xx.util.Path;
import xx.util.img.Img;
import xx.util.property.Config;
import xx.util.string.Format;

public class Upload
{
    Logger log = Logger.getLogger(this.getClass());

    /** request to handle */
    private HttpServletRequest request = null;

    private Param param;

    private Check check;

    private String root;

    public Upload(HttpServletRequest request) throws Exception
    {
        if (request == null)
            throw new Exception("非法访问！");

        this.request = request;
        param = new Param();
        check = new Check();
        String pageHtmlFolder = Config.getInstance().get(Constants.PAGE_HTML_FOLDER);
        root = Format.isEmpty(pageHtmlFolder)?Path.getRootPath():pageHtmlFolder;
    }

    public List<Item> go()
    {
        boolean isHtml5 = "application/octet-stream".equals(request.getContentType());
        Up up = isHtml5 ? new Up5() : new Up4();
        up.go();

        return up.getList();
    }

    public Upload setSmallMaxWidth(int width)
    {
        param.imageSmallMaxWidth = width;
        return this;
    }

    public Upload setSmallMaxHeight(int height)
    {
        param.imageSmallMaxHeight = height;
        return this;
    }

    public Upload setMiddleMaxWidth(int width)
    {
        param.imageMiddleMaxWidth = width;
        return this;
    }

    public Upload setMiddleMaxHeight(int height)
    {
        param.imageMiddleMaxHeight = height;
        return this;
    }

    public Upload setBigMaxWidth(int width)
    {
        param.imageBigMaxWidth = width;
        return this;
    }

    public Upload setBigMaxHeight(int height)
    {
        param.imageBigMaxHeight = height;
        return this;
    }

    public Upload setAllowedCount(int count)
    {
        param.allowedCount = count;
        return this;
    }

    public Upload setAllowedTypes(String... types)
    {
        param.allowedTypes = types;
        return this;
    }

    public Upload setRootFolder(String folder)
    {
        folder = folder.replaceAll("\\", "/");

        folder = "/".equals(folder.substring(0, 1)) ? folder : "/" + folder;
        folder = "/".equals(folder.substring(folder.length() - 1, folder.length())) ? folder : "/"
                + folder;

        param.folder = folder;
        return this;
    }

    public Upload setCutPosition(String position)
    {
        param.imageCutPosition = position;
        return this;
    }

    public Upload setWatermarkPosition(String position)
    {
        param.imageWatermarkPosition = position;
        return this;
    }

    public Upload setWatermarkText(String text)
    {
        param.imageWatermarkText = text;
        return this;
    }

    public Upload setWatermarkFile(File file)
    {
        param.imageWatermarkFile = file;
        return this;
    }

    public Upload setCut(boolean isCut)
    {
        param.isCut = isCut;
        return this;
    }

    public Upload setSmall(boolean isSmall)
    {
        param.isSmall = isSmall;
        return this;
    }

    public Upload setWatermark(boolean isWatermark)
    {
        param.isWatermark = isWatermark;
        return this;
    }

    public Upload setLimitedTypes(String... types)
    {
        param.limitedTypes = types;
        return this;
    }

    public Upload setMaxSize(int size)
    {
        param.maxSize = size;
        return this;
    }

    /** 上传成功监听器 */
    public interface Listener
    {
        /**
         * 上传成功
         * 
         * @param item
         * @author 陈祥 2011-9-26
         */
        public void succeed(Item item);
    }

    /** 配置文件 */
    class Key
    {
        /** 内容占用大小(默认值32K) 32*1024 */
        static final String MAX_MEMORY_SIZE = "upload_max_memory_size";

        /** 文件保存地址，默认：/uploadfile/ */
        static final String FOLDER = "upload_folder";

        /** 单个默认允许上传的最大值（默认2M） 2*1024*1024 */
        static final String MAX_SIZE = "upload_max_size";

        /** 不允许上传的文件类型，默认为：jsp,php,asp,js,css */
        static final String LINITED_TYPES = "upload_limited_types";

        /** 允许上传的文件类型，过虑掉upload_limited_types中的类型，默认为所有* */
        static final String ALLOWED_TYPES = "upload_allowed_types";

        /** 同一请求中允许上传的文件个数，默认为1个 */
        static final String ALLOWED_COUNT = "upload_allowed_count";

        /** 图片类型是否需要生成缩略图，默认为true，生成 */
        static final String IMAGE_SMALL = "upload_image_small";

        /** 生成小图的最大宽度，默认为100px */
        static final String IMAGE_SMALL_MAX_WIDTH = "upload_image_small_max_width";

        /** 生成小图的最大高度，默认为100px */
        static final String IMAGE_SMALL_MAX_HEIGHT = "upload_image_small_max_height";

        /** 生成中图的最大宽度，默认为260px */
        static final String IMAGE_MIDDLE_MAX_WIDTH = "upload_image_middle_max_width";

        /** 生成中图的最大高度，默认为260px */
        static final String IMAGE_MIDDLE_MAX_HEIGHT = "upload_image_middle_max_height";

        /** 生成大图的最大宽度，默认为800px */
        static final String IMAGE_BIG_MAX_WIDTH = "upload_image_big_max_width";

        /** 生成大图的最大高度，默认为600px */
        static final String IMAGE_BIG_MAX_HEIGHT = "upload_image_big_max_height";

        /** 图片比例不同的是否需要裁剪，默认为false，不裁剪 */
        static final String IMAGE_CUT = "upload_image_cut";

        /**
         * <B>图片裁剪位置，参照点为左上角（0,0）</B><BR>
         * 左上：left,top，左下：left,bottom，左中：left,center<BR>
         * 右上：right,top，右下：right,bottom，右中：right,center<BR>
         * 中上：center,top，中下：center,bottom，中中：center,center<BR>
         * 自定义位置：{num},{num}<BR>
         * 默认为：中中（center,center）<BR>
         * 例如：距离左边10px，上边10px，可以这样写：left+10,top+10
         */
        static final String IMAGE_CUT_POSITION = "upload_image_cut_position";

        /** 是否需要增加水印，默认为false，不增加 */
        static final String IMAGE_WATERMARK = "upload_image_watermark";

        /**
         * <B>水印位置，参照点为左上角（0,0）</B><BR>
         * 左上：left,top，左下：left,bottom，左中：left,center<BR>
         * 右上：right,top，右下：right,bottom，右中：right,center<BR>
         * 中上：center,top，中下：center,bottom，中中：center,center<BR>
         * 自定义位置：{num},{num}<BR>
         * 默认为：右下（right,bottom）<BR>
         * 例如：从距离左边10px，上边10px的地方开始裁剪，可以这样写：left+10,top+10
         */
        static final String IMAGE_WATERMARK_POSITION = "upload_image_watermark_position";
    }

    /** 所需参数 */
    class Param
    {
        /** 是否需要生成小图 */
        static final String IS_SMALL = "is_small";

        /** 小图最大宽度 */
        static final String SWIDTH = "swidth";

        /** 小图最大高度 */
        static final String SHEIGHT = "sheight";

        /** 中图最大宽度 */
        static final String MWIDTH = "mwidth";

        /** 中图最大高度 */
        static final String MHEIGHT = "mheight";

        /** 大图最大宽度 */
        static final String BWIDTH = "bwidth";

        /** 大图最大高度 */
        static final String BHEIGHT = "bheight";

        /** 是否需要裁剪 */
        static final String IS_CUT = "is_cut";

        /** 裁剪位置 */
        static final String CUT_POSITION = "cut_position";

        /** 是否需要生成水印 */
        static final String IS_WATERMARK = "is_watermark";

        /** 水印位置 */
        static final String WATERMAR_POSITION = "watermark_position";

        /** 允许上传的文件类型 */
        static final String ALLOWED_TYPES = "file_types";

        Config c;

        Param()
        {
            init();
        }

        /**
         * 内容占用大小(默认值32K) 32*1024<br>
         * upload_max_memory_size=32768
         */
        int maxMemorySize;

        /**
         * 文件保存地址<br>
         * upload_folder=/uploadfile/
         */
        String folder;

        /**
         * 单个默认允许上传的最大值（默认2M） 2*1024*1024<br>
         * upload_max_size=2097152
         */
        int maxSize;

        /**
         * 不允许上传的文件类型<br>
         * upload_limited_types=jsp,php,asp
         */
        String[] limitedTypes;

        /**
         * 允许上传的文件类型，过虑掉upload_limited_types中的类型，默认为所有<br>
         * upload_allowed_types=*
         */
        String[] allowedTypes;

        /**
         * 同一请求中允许上传的文件个数，默认为1个<br>
         * upload_allowed_count=1
         */
        int allowedCount;

        /**
         * 图片类型是否需要生成缩略图，默认为true，生成<br>
         * upload_image_small=true
         */
        boolean isSmall;

        /**
         * 生成小图的最大宽度，默认为100px<br>
         * upload_image_small_max_width=100
         */
        int imageSmallMaxWidth;

        /**
         * 生成小图的最大高度，默认为100px<br>
         * upload_image_small_max_height=100
         */
        int imageSmallMaxHeight;

        /**
         * 生成中图的最大宽度，默认为260px<br>
         * upload_image_middle_max_width=260
         */
        int imageMiddleMaxWidth;

        /**
         * 生成中图的最大高度，默认为260px<br>
         * upload_image_middle_max_height=260
         */
        int imageMiddleMaxHeight;

        /**
         * 生成大图的最大宽度，默认为800px<br>
         * upload_image_big_max_width=800
         */
        int imageBigMaxWidth;

        /**
         * 生成大图的最大高度，默认为600px<br>
         * upload_image_big_max_height=600
         */
        int imageBigMaxHeight;

        /**
         * 图片比例不同的是否需要裁剪，默认为false，不裁剪<br>
         * upload_image_cut=false
         */
        boolean isCut;

        /**
         * 图片裁剪位置，参照点为左上角（0,0）<br>
         * 左上：left,top，左下：left,bottom，左中：left,center<br>
         * 右上：right,top，右下：right,bottom，右中：right,center<br>
         * 中上：center,top，中下：center,bottom，中中：center,center<br>
         * 自定义位置：{num},{num}<br>
         * 默认为：中中（center,center）<br>
         * 例如：距离左边10px，上边10px，可以这样写：left+10,top+10<br>
         * upload_image_cut_position=center,center
         */
        String imageCutPosition;

        /**
         * 是否需要增加水印，默认为false，不增加<br>
         * upload_image_watermark=false
         */
        boolean isWatermark;

        /**
         * 水印位置，参照点为左上角（0,0）<br>
         * 左上：left,top，左下：left,bottom，左中：left,center<br>
         * 右上：right,top，右下：right,bottom，右中：right,center<br>
         * 中上：center,top，中下：center,bottom，中中：center,center<br>
         * 自定义位置：{num},{num}<br>
         * 默认为：右下（right,bottom）<br>
         * 例如：从距离左边10px，上边10px的地方开始裁剪，可以这样写：left+10,top+10<br>
         * upload_image_watermark_position=right,bottom
         */
        String imageWatermarkPosition;

        String imageWatermarkText;

        File imageWatermarkFile;

        void init()
        {
            c = Config.getInstance();
            maxMemorySize = c.getInt(Key.MAX_MEMORY_SIZE, 32 * 1024);
            folder = c.get(Key.FOLDER, "/uploadfile/");
            maxSize = c.getInt(Key.MAX_SIZE, 2 * 1024 * 1024);
            limitedTypes = Format.strToStrArr(c.get(Key.LINITED_TYPES, "jsp,php,asp,js,css"));
            allowedTypes = Format.strToStrArr(c.get(Key.ALLOWED_TYPES, "*"));
            allowedCount = c.getInt(Key.ALLOWED_COUNT, 1);
            isSmall = c.getBoolean(Key.IMAGE_SMALL, true);
            imageSmallMaxWidth = c.getInt(Key.IMAGE_SMALL_MAX_WIDTH, 100);
            imageSmallMaxHeight = c.getInt(Key.IMAGE_SMALL_MAX_HEIGHT, 100);
            imageMiddleMaxWidth = c.getInt(Key.IMAGE_MIDDLE_MAX_WIDTH, 260);
            imageMiddleMaxHeight = c.getInt(Key.IMAGE_MIDDLE_MAX_HEIGHT, 260);
            imageBigMaxWidth = c.getInt(Key.IMAGE_BIG_MAX_WIDTH, 800);
            imageBigMaxHeight = c.getInt(Key.IMAGE_BIG_MAX_HEIGHT, 600);
            isCut = c.getBoolean(Key.IMAGE_CUT, false);
            imageCutPosition = c.get(Key.IMAGE_CUT_POSITION, "center,center");
            isWatermark = c.getBoolean(Key.IMAGE_WATERMARK, false);
            imageWatermarkPosition = c.get(Key.IMAGE_WATERMARK_POSITION, "right,bottom");
        }

        @SuppressWarnings("unchecked")
        void initParameter(HttpServletRequest request)
        {
            Map<String, String> pars = request.getParameterMap();
            isSmall = Format.strToBoolean(pars.get(IS_SMALL), isSmall);
            if (isSmall)
            {
                imageSmallMaxWidth = Format.strToInt(pars.get(SWIDTH), imageSmallMaxWidth);
                imageSmallMaxHeight = Format.strToInt(pars.get(SHEIGHT), imageSmallMaxHeight);
                imageMiddleMaxWidth = Format.strToInt(pars.get(MWIDTH), imageMiddleMaxWidth);
                imageMiddleMaxHeight = Format.strToInt(pars.get(MHEIGHT), imageMiddleMaxHeight);
            }

            isCut = Format.strToBoolean(pars.get(IS_CUT), isCut);
            if (isCut)
            {
                String pos = pars.get(CUT_POSITION);
                imageCutPosition = Format.isEmpty(pos) ? imageCutPosition : pos;
            }

            isWatermark = Format.strToBoolean(IS_WATERMARK, isWatermark);
            if (isWatermark)
            {
                String pos = pars.get(WATERMAR_POSITION);
                imageWatermarkPosition = Format.isEmpty(pos) ? imageWatermarkPosition : pos;
            }

            String fileType = pars.get(ALLOWED_TYPES);
            allowedTypes = Format.isEmpty(fileType) ? allowedTypes : Format.strToStrArr(fileType);
        }
    }

    /** 验证上传文件是否合法等 */
    class Check
    {
        boolean isLimitedType(String type)
        {
            assert (param.limitedTypes != null);
            if (param.limitedTypes == null)
                return true;

            if (type == null)
                return (false);

            for (String str : param.limitedTypes)
                if (type.startsWith(str))
                    return (true);

            return false;
        }

        boolean isAllowedType(String type)
        {
            assert (param.allowedTypes != null);
            if (param.allowedTypes == null)
                return true;

            if (type == null)
                return false;

            if (param.allowedTypes.length > 0 && "*".equals(param.allowedTypes[0]))
                return true;

            for (String str : param.allowedTypes)
                if (type.startsWith(str))
                    return (true);

            return false;
        }

        boolean isAllowedCount(int count)
        {
            return param.allowedCount >= count;
        }
    }

    /** 上传的单个文件对象 */
    public class Item
    {
        String name;

        int size;

        String type;

        InputStream inputStream;

        private String orginalPath;

        private String smallPath;

        private String middlePath;

        private String bigPath;

        public InputStream getInputStream()
        {
            return inputStream;
        }

        public Item setInputStream(InputStream inputStream)
        {
            this.inputStream = inputStream;
            return this;
        }

        public String getName()
        {
            return name;
        }

        public Item setName(String name)
        {
            this.name = name;
            return this;
        }

        public int getSize()
        {
            return size;
        }

        public Item setSize(int size)
        {
            this.size = size;
            return this;
        }

        public String getType()
        {
            return type;
        }

        public Item setType(String type)
        {
            this.type = type;
            return this;
        }

        public String getOrginalPath()
        {
            return orginalPath.substring(root.length()).replaceAll("\\\\", "/");
        }

        public String getBigPath()
        {
            if (Format.isEmpty(bigPath))
                return null;
            return bigPath.substring(root.length()).replaceAll("\\\\", "/");
        }

        public String getMiddlePath()
        {
            if (Format.isEmpty(middlePath))
                return null;
            return middlePath.substring(root.length()).replaceAll("\\\\", "/");
        }

        public String getSmallPath()
        {
            if (Format.isEmpty(smallPath))
                return null;
            return smallPath.substring(root.length()).replaceAll("\\\\", "/");
        }

        boolean save()
        {
            saveOrginal();
            if (param.isSmall && Img.check(newInputStream(this.orginalPath)))
            {
                saveBig();
                saveMiddle();
                saveSmall();
            }
            return true;
        }

        private boolean saveOrginal()
        {
            String folder = root + param.folder + "orginal/" + getDatePath();
            File file = saveFile(folder, inputStream);
            if (file != null)
                this.orginalPath = file.getPath();
            return true;
        }

        private boolean saveBig()
        {
            String folder = root + param.folder + "big/" + getDatePath();

            File file = saveFile(folder, newInputStream(this.orginalPath));
            if (file != null)
            {
                Img img = new Img(file);
                img.small(param.imageBigMaxWidth, param.imageBigMaxHeight);

                this.bigPath = file.getPath();
            }
            return true;
        }

        private boolean saveMiddle()
        {
            String folder = root + param.folder + "middle/" + getDatePath();

            File file = saveFile(folder, newInputStream(this.bigPath));
            if (file != null)
            {
                Img img = new Img(file);
                img.small(param.imageMiddleMaxWidth, param.imageMiddleMaxHeight, param.isCut);

                this.middlePath = file.getPath();
            }
            return true;
        }

        private boolean saveSmall()
        {
            String folder = root + param.folder + "small/" + getDatePath();
            File file = saveFile(folder, newInputStream(this.middlePath));
            if (file != null)
            {
                Img img = new Img(file);
                img.small(param.imageSmallMaxWidth, param.imageSmallMaxHeight, param.isCut);
                this.smallPath = file.getPath();
            }
            return true;
        }

        private InputStream newInputStream(String path)
        {
            try
            {
                return new FileInputStream(new File(path));
            }
            catch (FileNotFoundException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return null;
            }
        }

        private File saveFile(String folder, InputStream is)
        {
            try
            {
                File file = this.createTempFile(folder, this.type);
                IOUtils.copy(is, new FileOutputStream(file));
                return file;
            }
            catch (IOException e)
            {
                log.error("保存文件" + name + "出错，错误message：" + e.getMessage());
                e.printStackTrace();
                return null;
            }
        }

        /**
         * 返回以当前日期构造的路径 eg："2010\10\19\"
         * 
         * @return
         * @author dengjinye,2010-11-19
         */
        private String getDatePath()
        {
            Calendar cal = Calendar.getInstance();
            StringBuilder datePath = new StringBuilder();
            return datePath.append(cal.get(Calendar.YEAR)).append("/").append(
                    cal.get(Calendar.MONTH) + 1).append("/").append(cal.get(Calendar.DATE)).append(
                    "/").toString();
        }

        private File createTempFile(String folder, String suffix)
        {
            suffix = suffix != null && !"".equals(suffix) ? suffix : ".fid";

            File folderPath = new File(folder);
            if (!folderPath.exists())
                folderPath.mkdirs();
            File tmp = null;
            try
            {
                tmp = File.createTempFile("img", "." + suffix, folderPath);
            }
            catch (IOException e)
            {
                e.printStackTrace();
                log.error(e);
            }

            return tmp;
        }
    }

    /** HTML4上传 */
    class Up4 implements Up
    {
        /** */
        private FileItemFactory fileItemFactory = null;

        /** */
        private ServletFileUpload servletUpload = null;

        private List<FileItem> items = null;

        public List<Item> list = new ArrayList<Item>();

        Up4()
        {
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (!isMultipart)
                throw new IllegalArgumentException("request isn't a file upload request!");

            fileItemFactory = new DiskFileItemFactory(param.maxMemorySize, new File(System
                    .getProperty("java.io.tmpdir")));
            servletUpload = new ServletFileUpload(fileItemFactory);
            initItems();

            list = new ArrayList<Item>();
        }

        @SuppressWarnings("unchecked")
        private void initItems()
        {
            try
            {
                items = this.servletUpload.parseRequest(request);
            }
            catch (FileUploadException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        public void go()
        {
            if (items == null)
                return;

            int count = 0;
            Iterator iter = items.iterator();
            while (iter.hasNext())
            {
                FileItem fileItem = (FileItem) iter.next();
                // 文件域
                if (!fileItem.isFormField() && !"".equals(fileItem.getName()))
                {
                    if (!check.isAllowedCount(++count))
                        break;

                    String fileType = fileItem.getContentType().toString();
                    if (check.isLimitedType(fileType))
                        continue;

                    if (!check.isAllowedType(fileType))
                        continue;

                    String fileName = fileItem.getName().substring(
                            fileItem.getName().lastIndexOf("\\") + 1);
                    String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);

                    Item item = new Item();
                    item.name = fileName;
                    item.type = suffix;
                    item.inputStream = getInputStream(fileItem);
                    item.size = (int) fileItem.getSize();
                    item.save();

                    list.add(item);
                }
            }
        }

        private InputStream getInputStream(FileItem fileItem)
        {
            try
            {
                return fileItem.getInputStream();
            }
            catch (IOException e)
            {
                return null;
            }
        }

        public List<Item> getList()
        {
            return list;
        }
    }

    /** HTML5上传 */
    class Up5 implements Up
    {
        public List<Item> list;

        Up5()
        {
            list = new ArrayList<Item>();
        }

        public void go()
        {
            String dispoString = request.getHeader("Content-Disposition");

            int iFindStart = dispoString.indexOf("filename=\"") + 10;

            int iFindEnd = dispoString.indexOf("\"", iFindStart);

            String fileName = dispoString.substring(iFindStart, iFindEnd);

            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);

            Item item = new Item();
            item.name = fileName;
            item.type = suffix;
            item.inputStream = getInputStream();
            item.size = request.getContentLength();
            item.save();

            list.add(item);
        }

        private InputStream getInputStream()
        {
            try
            {
                return request.getInputStream();
            }
            catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }

        public List<Item> getList()
        {
            return list;
        }
    }

    interface Up
    {
        public List<Item> getList();

        public void go();
    }
}
